package yuhuibear.poat.object.bean;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import yuhuibear.poat.exception.MissAttributesOfBean;

/**
 * 
 * @author yuhuibear
 */
public class BeanConstructor {

	private static final Set<Class<?>> singleValues = new HashSet<Class<?>>();
	{
		singleValues.add(Integer.class);
		singleValues.add(Double.class);
		singleValues.add(String.class);
		singleValues.add(Date.class);
		singleValues.add(Long.class);
		singleValues.add(Float.class);
	}

	private Map<String, ResultSetMetaData> resultMetaDataMap;

	private PersistentClassLoader structs;

	public BeanConstructor(PersistentClassLoader structs) {
		resultMetaDataMap = Collections.synchronizedMap(new HashMap<String, ResultSetMetaData>());
		this.structs = structs;
	}

	public <T> List<T> constructList(String sql, ResultSet rs, Class<T> type)
			throws Exception {

		List<T> result = new LinkedList<T>();
		Object tobjt;
		if (singleValues.contains(type)) {
			while (rs.next()) {
				tobjt = rs.getObject(1);
				if (tobjt.getClass() == Long.class && type == Integer.class) {
					result.add(type.cast((Long.class.cast(tobjt)).intValue()));
				}
				else if (tobjt.getClass() == Double.class
						&& type == Float.class) {
					result.add(type.cast((Double.class.cast(tobjt)).intValue()));
				}
				else if (tobjt.getClass() == BigDecimal.class) {
					if (type == Float.class)
						result.add(type.cast(BigDecimal.class.cast(tobjt).floatValue()));
					else if (type == Double.class)
						result.add(type.cast(BigDecimal.class.cast(tobjt).doubleValue()));
					else if (type == Long.class)
						result.add(type.cast(BigDecimal.class.cast(tobjt).longValue()));
					else if (type == Integer.class)
						result.add(type.cast(BigDecimal.class.cast(tobjt).intValue()));
				}
				else if (tobjt.getClass() == BigInteger.class) {
					if (type == Integer.class)
						result.add(type.cast(BigInteger.class.cast(tobjt).intValue()));
					else if (type == Double.class)
						result.add(type.cast(BigInteger.class.cast(tobjt).longValue()));
				}
				else {
					result.add(type.cast(tobjt));
				}
			}
		}
		else {
			Map<String, AttributeMapper> mapperLst = this.structs.analyse(type).getAttributes();
			ResultSetMetaData rsmd = this.getResultMetaData(sql, rs);
			while (rs.next()) {
				result.add(createBean(rsmd, rs, mapperLst, type));
			}
		}
		return result;
	}

	public <T> T construct(String sql, ResultSet rs, Class<T> type)
			throws Exception {

		Object result = null;
		Object rsObj;
		if (singleValues.contains(type)) {
			while (rs.next()) {
				rsObj = rs.getObject(1);
				if (rsObj.getClass() == Long.class && type == Integer.class) {
					result = type.cast((Long.class.cast(rsObj)).intValue());
				}
				else if (rsObj.getClass() == Double.class
						&& type == Float.class) {
					result = type.cast((Double.class.cast(rsObj)).intValue());
				}
				else if (rsObj.getClass() == BigDecimal.class) {
					if (type == Float.class)
						result = type.cast(BigDecimal.class.cast(rsObj).floatValue());
					else if (type == Double.class)
						result = type.cast(BigDecimal.class.cast(rsObj).doubleValue());
					else if (type == Long.class)
						result = type.cast(BigDecimal.class.cast(rsObj).longValue());
					else if (type == Integer.class)
						result = type.cast(BigDecimal.class.cast(rsObj).intValue());
				}
				else if (rsObj.getClass() == BigInteger.class) {
					if (type == Integer.class)
						result = type.cast(BigInteger.class.cast(rsObj).intValue());
					else if (type == Double.class)
						result = type.cast(BigInteger.class.cast(rsObj).longValue());
				}
				else {
					result = type.cast(rsObj);
				}
			}
		}
		else {
			ResultSetMetaData rsmd = this.getResultMetaData(sql, rs);
			Map<String, AttributeMapper> mapper = this.structs.analyse(type).getAttributes();
			while (rs.next()) {
				result = createBean(rsmd, rs, mapper, type);
			}
		}
		if (result == null) {
			return null;
		}
		else {
			return type.cast(result);
		}
	}

	private <T> T createBean(ResultSetMetaData rsmd, ResultSet rset,
			Map<String, AttributeMapper> mapperLst, Class<T> type)
			throws Exception {
		Object obj = type.newInstance();
		for (int i = 1; i <= rsmd.getColumnCount(); i++) {
			AttributeMapper mapper = mapperLst.get(rsmd.getColumnName(i));
			if (mapper == null) { throw new MissAttributesOfBean(type, rsmd.getColumnName(i)); }
			mapper.setValue(obj, rset.getObject(mapper.getDBName()));
		}
		return type.cast(obj);
	}

	private ResultSetMetaData getResultMetaData(String sql, ResultSet rs)
			throws Exception {
		ResultSetMetaData rsmd = null;
		if (resultMetaDataMap.containsKey(sql)) {
			rsmd = resultMetaDataMap.get(sql);
		}
		else {
			rsmd = rs.getMetaData();
			resultMetaDataMap.put(sql, rsmd);
		}
		return rsmd;
	}
}
